package core;

import event.InvitationTimedOutEventArgs;
import game.*;
import io.XMLInteractor;

import java.io.File;
import java.util.*;

import messages.InvitationTimedOutMessage;


public class GameManager {

	public GameManager() {
		loadGames();		
		initializeInvitationTimeoutThread();	
	}


	/**
	 * Adds a new match to the list of ocurring matchs.
	 * 
	 * @param match The new match to add.
	 */
	public synchronized void addMatch(Match match) {
		this.matchs.put(match.getId(), match);
	}
	
	
	/**
	 * Checks if a specified match exists.
	 * 
	 * @param matchId True if the match exists, false otherwise
	 */
	public synchronized boolean containsMatch(int matchId) {
		return this.matchs.containsKey(matchId);
	}
	
	
	/**
	 * Returns a match with a given ID
	 * 
	 * @param matchId The ID of the match to retrieve
	 * @return The correspondent match.
	 */
	public synchronized Match getMatch(int matchId) {
		return this.matchs.get(matchId);
	}
	
	
	/**
	 * Removes the Match with the given ID.
	 * 
	 * @param id The ID of the Match to be removed.
	 * @return The removed Match.
 	 */
	public synchronized Match removeMatch(int matchId) {
		return this.matchs.remove(matchId);
	}
	
	
	/**
	 * Returns the list of active matchs
	 * 
	 * @return The list of active matches.
	 */
	public synchronized Collection<Match> getActiveMatchs() {		
		return matchs.values();
	}
	
	
	/**
	 * Returns the matchs where a given player participates.
	 * 
	 * @param player The player whose matchs we want.
	 * @return The list of matches this player is playing.
	 */
	public synchronized List<Match> getActiveMatchsByPlayer(Player player) {
		List<Match> list = new ArrayList<Match>();
		
		for (Map.Entry<Integer, Match> entry : matchs.entrySet()) {
			if (entry.getValue().getPlayers().contains(player)) {
				list.add(entry.getValue());
			}
		}
		
		return list;
	}
	
	

	/**
	 * Returns the matchs where a given player participated.
	 * 
	 * @param player The player whose matchs we want.
	 * @return The list of matches this player participated in.
	 */
	public synchronized List<MatchStatistics> getOldMatchsByPlayer(Player player) {
		List<MatchStatistics> list = new ArrayList<MatchStatistics>();
		
		List<Integer> matchIds = findOldMatchIdsByPlayer(player);
		for (int matchId : matchIds) {
			list.add(XMLInteractor.loadMatch(matchId));
		}
		
		return list;
	}
	

	/**
	 * Adds a new Invitation to the list of pending invitations
	 * 
	 * @param invitation The Invitation to add.
	 */
	public synchronized void addInvitation(Invitation invitation) {
		this.pendingInvitations.put(invitation.getId(), invitation);
	}
	
	
	/**
	 * Returns the Invitation with the given ID.
	 * 
	 * @param id The ID of the Invitation to obtain.
	 * @return The Invitation with the given ID.
	 */
	public synchronized Invitation getInvitation(int id) {
		return this.pendingInvitations.get(id);
	}
	
	
	/**
	 * Returns the pending invitations where a given player participates.
	 * 
	 * @param player The player whose invitations we want.
	 * @return The list of pending invitations with this player on.
	 */
	public synchronized List<Invitation> getInvitationsWithPlayer(Player player) {
		List<Invitation> list = new ArrayList<Invitation>();
		
		for (Map.Entry<Integer, Invitation> entry : pendingInvitations.entrySet()) {
			if (entry.getValue().getDestiny().equals(player) ||
					entry.getValue().getSender().equals(player)) {
				list.add(entry.getValue());
			}
		}
		
		return list;
	}
	
	
	/**
	 * Checks if there is an Invitation with a given ID
	 * 
	 * @param id The ID of the Invitation
	 * @return True if the Invitation exists, false otherwise
	 */
	public synchronized boolean containsInvitation(int id) {
		return this.pendingInvitations.containsKey(id);
	}
	
		
	/**
	 * Removes an Invitation with the given ID.
	 * 
	 * @param id The ID of the Invite to be removed.
	 * @return The removed invitation.
 	 */
	public synchronized Invitation removeInvitation(int id){
		return this.pendingInvitations.remove(id);
	}
	
	
	/**
	 * Returns a Game when given its name.
	 * 
	 * @param gameName The name of the Game to obtain.
	 * @return The Game with the given name.
	 */
	public synchronized Game getGameByName(String gameName) {
		return this.games.get(gameName);
	}
	
	
	/**
	 * Returns the list of available games
	 * 
	 * @return The list of available games
	 */
	public synchronized Collection<Game> getGames() {
		return this.games.values();
	}
	
	
	/**
	 * Checks if a given name exists.
	 * 
	 * @param gameName The name of the Game to check.
	 * @return True if the game exists, false otherwise.
	 */
	public synchronized boolean containsGame(String gameName) {
		return this.games.containsKey(gameName);
	}
	
	
	/**
	 * Initializes the list of known games
	 */
	@SuppressWarnings("unchecked")
	private synchronized void loadGames() {
		this.games = XMLInteractor.loadGames();
	}


	/**
	 * Saves the list of known games
	 */
	private synchronized void saveGames() {
		XMLInteractor.saveGames(games.values());
	}
	
	
	private List<Integer> findOldMatchIdsByPlayer(Player player) {
		List<Integer> matchIds = new ArrayList<Integer>();
				
		File dir = new File(System.getProperty("user.dir") + "/data");
		for (String fileName : dir.list()) {
			
			if (fileName.matches(".*\\[.*" + player.getLogin() + ".*\\].*")) {
				String[] words = fileName.split(" ");
				matchIds.add(Integer.parseInt(words[words.length - 1]));
			}
		}
		
		return matchIds;
	}
	
	
	/**
	 * Creates a thread responsible for cleaning old invitations
	 */
	private void initializeInvitationTimeoutThread() {
		TimerTask pendingInvitationsTimeoutTask = new TimerTask() {
		    public void run() {
		    	Calendar now = new GregorianCalendar();
				Calendar then = new GregorianCalendar();
				
				List<Integer> toRemove = new ArrayList<Integer>();
				
				synchronized (pendingInvitations) {
				
					// Iterates over pendingInvitations and adds old invitations
					// to toRemove
			    	for (int invitationId : pendingInvitations.keySet()) {
			    		Invitation invitation = pendingInvitations.get(invitationId);
			    		
			    		then.setTime(invitation.getDateSent());
			    		then.add(Calendar.SECOND, 30);
			    		
			    		if (now.after(then)) {
			    			toRemove.add(invitationId);
			    		}
			    	}
			    	
			    	// Finally: remove the old invitations
			    	for (int invitationId : toRemove) {
			    		Invitation invitation = pendingInvitations.get(invitationId);  		
			    		
			    		pendingInvitations.remove(invitationId);
			    		
			    		Controller.getInstance().fireInvitationTimedOutEvent(
			    				new InvitationTimedOutEventArgs(invitation, new InvitationTimedOutMessage(invitationId)));
			    	}
			    }
		    }
		};
		
		Timer timer = new Timer();
        timer.schedule(pendingInvitationsTimeoutTask, 0, 10 * 1000);
	}
	
	
	// Which games do exist?
	private Map<String, Game> games = new HashMap<String, Game>();
	
	// Started matchs.
	private Map<Integer, Match> matchs = new HashMap<Integer, Match>();
	
	// Invitations sent but not yet answered.
	private Map<Integer, Invitation> pendingInvitations = new HashMap<Integer, Invitation>();
	
	
	private static final long serialVersionUID = 1L;
}
